package com.atguigu.daijia.map.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.constant.SystemConstant;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.Result;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.common.util.LocationUtil;
import com.atguigu.daijia.driver.client.DriverInfoFeignClient;
import com.atguigu.daijia.map.repository.OrderServiceLocationRepository;
import com.atguigu.daijia.map.service.LocationService;
import com.atguigu.daijia.model.entity.driver.DriverSet;
import com.atguigu.daijia.model.entity.map.OrderServiceLocation;
import com.atguigu.daijia.model.form.map.OrderServiceLocationForm;
import com.atguigu.daijia.model.form.map.SearchNearByDriverForm;
import com.atguigu.daijia.model.form.map.UpdateDriverLocationForm;
import com.atguigu.daijia.model.form.map.UpdateOrderLocationForm;
import com.atguigu.daijia.model.vo.map.NearByDriverVo;
import com.atguigu.daijia.model.vo.map.OrderLocationVo;
import com.atguigu.daijia.model.vo.map.OrderServiceLastLocationVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.domain.geo.GeoReference;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {
    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private MongoTemplate mongoTemplate;
    /**
     * 更新司机经纬度位置
     * @param updateDriverLocationForm
     * @return 是否更新成功
     */
    @Override
    public Boolean updateLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        //使用Redis中的GEO数据结构存储司机地理位置坐标
        String key = RedisConstant.DRIVER_GEO_LOCATION;
        Point point = new Point(
                 updateDriverLocationForm.getLongitude().doubleValue()
                ,updateDriverLocationForm.getLatitude().doubleValue()
        );
        stringRedisTemplate.opsForGeo().add(key, point, updateDriverLocationForm.getDriverId().toString());
        return true;
    }

    /**
     * 删除司机经纬度坐标
     *
     * @param driverId
     * @return
     */
    @Override
    public Boolean removeDriver(Long driverId) {
        stringRedisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION,driverId.toString());
        return true;
    }

    /**
     * 查询附近可以接单的司机集合
     *
     * @param searchNearByDriverForm
     * @return
     */
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        Point point = new Point(
                searchNearByDriverForm.getLongitude().doubleValue(),
                searchNearByDriverForm.getLatitude().doubleValue()); //经纬度
        GeoReference geoReference = GeoReference.fromCoordinate(point); //查询方式
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = stringRedisTemplate.opsForGeo().search
                (RedisConstant.DRIVER_GEO_LOCATION,
                        geoReference,
                        new Distance(SystemConstant.NEARBY_DRIVER_RADIUS,Metrics.KILOMETERS), //5000米以内
                        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
                                .includeDistance() //返回值包括距离值
                                .includeCoordinates() //返回值包含坐标
                                .sortAscending() //升序排序
                );
        if (geoResults == null) {
            return Collections.emptyList();
        }
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = geoResults.getContent();
        if (content.isEmpty()) {
            return Collections.emptyList();
        }
        List<NearByDriverVo> nearByDriverVoList = new ArrayList<>();
        //遍历司机集合信息 根据司机个性设置信息排除不符合信息的司机
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoLocation : content) { //遍历返回结果集合
            RedisGeoCommands.GeoLocation<String> location = geoLocation.getContent(); //获取返回结果值 司机id
            String driverId = location.getName(); //获取司机Id

            Result<DriverSet> driverSet = driverInfoFeignClient.getDriverSet(Long.valueOf(driverId));
            if (driverSet == null||driverSet.getData() == null||driverSet.getCode()!=200) {
                continue;
            }
            DriverSet setData = driverSet.getData();
            if (setData.getServiceStatus() == 0) {
                //没有开始接单
                continue;
            }
            //判断当前司机是否设置订单里程设置
            BigDecimal orderDistance = setData.getOrderDistance();
            //如果订单里程设置等于0 则没有限制 否则只接里程在限制之内距离的订单
            if (orderDistance.doubleValue()!=0
                    && orderDistance.subtract(searchNearByDriverForm.getMileageDistance()).doubleValue()<0) {
                continue;
            }
            //判断当前司机是否设置有接单里程设置 并判断司机到接单点距离是否超过设置接单距离
            Distance distance = geoLocation.getDistance();//获取地理位置距离
            //当前接单距离
            BigDecimal currentDistance =
                    BigDecimal.valueOf(distance.getValue()).setScale(2, RoundingMode.HALF_UP);
            if (setData.getAcceptDistance().doubleValue()!=0 &&
                    setData.getAcceptDistance().subtract(currentDistance).doubleValue()<0) {
                continue;
            }

            //封装符合条件的司机信息
            NearByDriverVo nearByDriverVo = new NearByDriverVo();
            nearByDriverVo.setDriverId(Long.valueOf(driverId));
            nearByDriverVo.setDistance(currentDistance);
            nearByDriverVoList.add(nearByDriverVo);
        }
        return nearByDriverVoList;
    }

    /**
     * 更新订单位置到缓存中
     *
     * @param updateOrderLocationForm
     * @return
     */
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        //更新司机位置信息到缓存中
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());
        stringRedisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION+updateOrderLocationForm.getOrderId()
                , JSONObject.toJSONString(orderLocationVo)
                );
        return true;
    }

    /**
     * 获取缓存的司机当前位置信息
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderLocationVo getOrderLocation(Long orderId) {
        String key = RedisConstant.UPDATE_ORDER_LOCATION+orderId;
        String json = stringRedisTemplate.opsForValue().get(key);
        OrderLocationVo orderLocationVo = JSONObject.parseObject(json, OrderLocationVo.class);
        return orderLocationVo;
    }

    //批量保存订单地理位置到MongDB中
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
        List<OrderServiceLocation> orderServiceLocations = new ArrayList<>();
        for (OrderServiceLocationForm orderServiceLocationForm : orderLocationServiceFormList) {
            OrderServiceLocation serviceLocation = new OrderServiceLocation();
            serviceLocation.setId(ObjectId.get().toString());
            serviceLocation.setOrderId(orderServiceLocationForm.getOrderId());
            serviceLocation.setLongitude(orderServiceLocationForm.getLongitude());
            serviceLocation.setLatitude(orderServiceLocationForm.getLatitude());
            serviceLocation.setCreateTime(new Date());
        }
        mongoTemplate.insertAll(orderServiceLocations);
        return true;
    }

    /**
     * 获取当前订单的最后位置信息
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));
        query.limit(1);
        query.with(Sort.by(Sort.Order.desc("createTime")));
        OrderServiceLocation location = mongoTemplate.findOne(query, OrderServiceLocation.class);
        if (!Optional.ofNullable(location).isPresent()) {
            throw new GuiguException(ResultCodeEnum.FAIL);
        }
        OrderServiceLastLocationVo lastLocationVo = new OrderServiceLastLocationVo();
        BeanUtils.copyProperties(location,lastLocationVo);
        return lastLocationVo;
    }

    /**
     * 计算实际订单里程
     *
     * @param orderId
     * @return
     */
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        //拿出MongDB中存入的所有的地理位置信息 按照时间进行排序
        Query query = new Query();
        query.addCriteria( Criteria.where("orderId").is(orderId))
                        .with(Sort.by(Sort.Order.asc("createTime")));

        List<OrderServiceLocation> list = mongoTemplate.find(query, OrderServiceLocation.class);

        //计算每两个位置之间的距离信息并相加

        double realDistance = 0;
        if(!list.isEmpty()) {
            for (int i = 0; i < list.size()-1; i++) {
                OrderServiceLocation s1 = list.get(i);
                OrderServiceLocation s2 = list.get(i + 1);
                //计算距离
                double distance = LocationUtil.getDistance(s1.getLatitude().doubleValue(),
                        s1.getLongitude().doubleValue(),
                        s2.getLatitude().doubleValue(),
                        s2.getLongitude().doubleValue());
                realDistance+=distance;
            }
        }

        //返回距离
        return BigDecimal.valueOf(realDistance);
    }


}
